3-4 接口安全:内置序列化拦截器定制响应数据结构
本节讲解 NestJS 内置的 ClassSerializerInterceptor 与 class-transformer 装饰器的配合使用,通过声明式方式实现响应数据的脱敏控制。
序列化与反序列化
在 NestJS 请求处理流程中存在两个 DTO 阶段:
请求方向(反序列化):
前端 JSON → class-transformer 转为 class 实例 → class-validator 校验 → Controller
响应方向(序列化):
Controller 返回数据 → ClassSerializerInterceptor → class-transformer 过滤敏感字段 → 前端 JSON
text
- 反序列化:将请求数据转换为 class 实例(已通过
ValidationPipe+class-validator实现) - 序列化:将响应数据按 DTO 规则过滤后返回给前端(本节重点)
使用 ClassSerializerInterceptor
第一步:创建响应 DTO
// dto/public-user.dto.ts
import { IsString } from 'class-validator';
import { Exclude } from 'class-transformer';
export class PublicUserDto {
@IsString()
id: string;
@IsString()
username: string;
@Exclude() // 排除 password 字段
password: string;
constructor(partial: Partial<PublicUserDto>) {
Object.assign(this, partial);
}
}
typescript
第二步:在控制器中应用
// auth/auth.controller.ts
import { ClassSerializerInterceptor } from '@nestjs/common';
@Controller('auth')
export class AuthController {
@UseInterceptors(ClassSerializerInterceptor)
@Post('signup')
async signUp(@Body() dto: SignUpDto): Promise<PublicUserDto> {
const user = await this.authService.signUp(dto);
return new PublicUserDto(user);
}
}
typescript
@Exclude() 装饰器标记的字段不会出现在接口响应中。
DTO 类继承实现复用
当多个 DTO 共享大量字段时,可以通过继承减少重复代码:
// 基础 DTO
export class BaseUserDto {
id: string;
username: string;
}
// 公开 DTO — 排除 password
export class PublicUserDto extends BaseUserDto {
@Exclude()
password: string;
constructor(partial: Partial<PublicUserDto>) {
super();
Object.assign(this, partial);
}
}
// 详细 DTO — 排除更多敏感字段
export class PublicUserDetailDto extends BaseUserDto {
@Exclude()
password: string;
@Exclude()
email: string;
constructor(partial: Partial<PublicUserDetailDto>) {
super();
Object.assign(this, partial);
}
}
typescript
继承链可以无限扩展,只需在子类中标记需要排除的字段即可。
class-transformer 核心装饰器
| 装饰器 | 作用 |
|---|---|
@Exclude() | 标记该属性不参与序列化输出 |
@Expose() | 标记该属性参与序列化输出(配合 excludeExtraneousValues) |
@Type(() => Date) | 指定属性的目标类型 |
@Transform() | 自定义属性转换逻辑 |
全局注册 ClassSerializerInterceptor
// main.ts
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
typescript
全局注册后,所有返回 class 实体的接口都会自动进行序列化处理,无需在每个路由上单独添加 @UseInterceptors(ClassSerializerInterceptor)。
内置拦截器的局限性
ClassSerializerInterceptor 存在以下不足:
- 写法冗长:每次都需要
new PublicUserDto(user)创建实例 - 手动赋值:DTO 需要
constructor+Object.assign来初始化属性 - 不够灵活:无法通过参数控制序列化策略
下一节将介绍如何通过自定义装饰器解决这些问题。
小结
| 知识点 | 要点 |
|---|---|
| 序列化方向 | 响应方向的数据过滤,与请求方向的校验(反序列化)互补 |
@Exclude() | 标记字段不输出到 JSON 响应 |
ClassSerializerInterceptor | NestJS 内置序列化拦截器,配合 class-transformer 工作 |
| DTO 继承 | 通过 extends 复用公共字段,减少重复 |
| 全局注册 | app.useGlobalInterceptors() 对所有接口生效 |
↑